home *** CD-ROM | disk | FTP | other *** search
/ Linux Cubed Series 3: Developer Tools / Linux Cubed Series 3 - Developer Tools.iso / devel / lang / lisp / guile-ii.src / guile-ii / guile-src / libguile / marksweep.c < prev    next >
Encoding:
C/C++ Source or Header  |  1995-08-17  |  14.4 KB  |  669 lines

  1. /* The way of garbage collecting which allows use of the cstack is due to
  2.  * SIOD by George Carrette.
  3.  */
  4.  
  5. #include <stdio.h>
  6. #include "_scm.h"
  7.  
  8.  
  9.  
  10.  
  11. #ifdef __STDC__
  12. SCM 
  13. scm_gc_for_newcell (void)
  14. #else
  15. SCM 
  16. scm_gc_for_newcell ()
  17. #endif
  18. {
  19.   SCM fl;
  20.   scm_gc_for_alloc (1, &scm_freelist);
  21.   fl = scm_freelist;
  22.   scm_freelist = CDR (fl);
  23.   return fl;
  24. }
  25.  
  26. static char s_bad_type[] = "unknown type in ";
  27. jmp_buf scm_save_regs_gc_mark;
  28.  
  29.  
  30. #define HUGE_LENGTH(x) (LENGTH_MAX==LENGTH(x) ? *((long *)VELTS(x)) : LENGTH(x))
  31.  
  32. #ifdef __STDC__
  33. void 
  34. scm_gc_sweep (void)
  35. #else
  36. void 
  37. scm_gc_sweep ()
  38. #endif
  39. {
  40.   register CELLPTR ptr;
  41. #ifdef POINTERS_MUNGED
  42.   register SCM scmptr;
  43. #else
  44. #define scmptr (SCM)ptr
  45. #endif
  46.   register SCM nfreelist;
  47.   register SCM *hp_freelist;
  48.   register long n;
  49.   register long m;
  50.   register sizet j;
  51.   register int span;
  52.   sizet i;
  53.   sizet seg_size;
  54.  
  55.   n = 0;
  56.   m = 0;
  57.   i = 0;
  58.  
  59.   while (i < scm_n_heap_segs)
  60.     {
  61.       hp_freelist = scm_heap_table[i].freelistp;
  62.       nfreelist = EOL;
  63.       span = scm_heap_table[i].ncells;
  64.       ptr = CELL_UP (scm_heap_table[i].bounds[0]);
  65.       seg_size = CELL_DN (scm_heap_table[i].bounds[1]) - ptr;
  66.       ++i;
  67.       for (j = seg_size + span; j -= span; ptr += span)
  68.     {
  69. #ifdef POINTERS_MUNGED
  70.       scmptr = PTR2SCM (ptr);
  71. #endif
  72.       switch TYP7 (scmptr)
  73.         {
  74.         case tcs_cons_gloc:
  75.           if (GCMARKP (scmptr))
  76.         {
  77.           if (CDR (CAR (scmptr) - 1) == (SCM)1)
  78.             CDR (CAR (scmptr) - 1) = (SCM)0;
  79.           goto cmrkcontinue;
  80.         }
  81.           {
  82.         SCM vcell;
  83.         vcell = CAR (scmptr) - 1L;
  84.         if ((CDR (vcell) == 0) || (CDR (vcell) == 1))
  85.           {
  86.             free ((char *)CDR (scmptr));
  87.             m += sizeof (SCM) * (LENGTH (((SCM *)vcell)[struct_i_format]));
  88.             CDR (scmptr) = BOOL_F;
  89.             --((SCM *)vcell)[struct_i_refcnt];
  90.           }
  91.           }
  92.           break;
  93.         case tcs_cons_imcar:
  94.         case tcs_cons_nimcar:
  95.         case tcs_closures:
  96.           if (GCMARKP (scmptr))
  97.         goto cmrkcontinue;
  98.           break;
  99.         case tc7_vector:
  100.         case tc7_lvector:
  101. #ifdef CCLO
  102.         case tc7_cclo:
  103. #endif
  104.           if (GC8MARKP (scmptr))
  105.         goto c8mrkcontinue;
  106.           m += (LENGTH (scmptr) * sizeof (SCM));
  107.         freechars:
  108.           scm_must_free (CHARS (scmptr));
  109.           /*    SETCHARS(scmptr, 0);*/
  110.           break;
  111.         case tc7_bvect:
  112.           if GC8MARKP (scmptr)
  113.         goto c8mrkcontinue;
  114.           m += sizeof (long) * ((HUGE_LENGTH (scmptr) + LONG_BIT - 1) / LONG_BIT);
  115.           goto freechars;
  116.         case tc7_ivect:
  117.         case tc7_uvect:
  118.           if GC8MARKP (scmptr)
  119.         goto c8mrkcontinue;
  120.           m += HUGE_LENGTH (scmptr) * sizeof (long);
  121.           goto freechars;
  122.         case tc7_fvect:
  123.           if GC8MARKP (scmptr)
  124.         goto c8mrkcontinue;
  125.           m += HUGE_LENGTH (scmptr) * sizeof (float);
  126.           goto freechars;
  127.         case tc7_dvect:
  128.           if GC8MARKP (scmptr)
  129.         goto c8mrkcontinue;
  130.           m += HUGE_LENGTH (scmptr) * sizeof (double);
  131.           goto freechars;
  132.         case tc7_cvect:
  133.           if GC8MARKP (scmptr)
  134.         goto c8mrkcontinue;
  135.           m += HUGE_LENGTH (scmptr) * 2 * sizeof (double);
  136.           goto freechars;
  137.         case tc7_string:
  138.           if (GC8MARKP (scmptr))
  139.         goto c8mrkcontinue;
  140.           m += HUGE_LENGTH (scmptr) + 1;
  141.           goto freechars;
  142.         case tc7_msymbol:
  143.           if (GC8MARKP (scmptr))
  144.         goto c8mrkcontinue;
  145.           m += LENGTH (scmptr) + 1;
  146.           scm_must_free ((char *)SLOTS (scmptr));
  147.           break;
  148.         case tc7_contin:
  149.           if GC8MARKP (scmptr)
  150.         goto c8mrkcontinue;
  151.           m += LENGTH (scmptr) * sizeof (STACKITEM) + sizeof (regs);
  152.           goto freechars;
  153.         case tc7_ssymbol:
  154.           if GC8MARKP(scmptr)
  155.         goto c8mrkcontinue;
  156.           break;
  157.         case tcs_subrs:
  158.           continue;
  159.         case tc7_port:
  160.           if GC8MARKP (scmptr)
  161.         goto c8mrkcontinue;
  162.           if OPENP (scmptr)
  163.         {
  164.           int k = PTOBNUM (scmptr);
  165.           if (!(k < scm_numptob))
  166.             goto sweeperr;
  167.           /* Keep "revealed" ports alive.  */
  168.           if (scm_revealed_count(scmptr) > 0)
  169.             continue;
  170.           /* Yes, I really do mean scm_ptobs[k].free */
  171.           /* rather than ftobs[k].close.  .close */
  172.           /* is for explicit CLOSE-PORT by user */
  173.           (scm_ptobs[k].free) (STREAM (scmptr));
  174.           scm_remove_from_port_table (scmptr);
  175.           scm_gc_ports_collected++;
  176.           SETSTREAM (scmptr, 0);
  177.           CAR (scmptr) &= ~OPN;
  178.         }
  179.           break;
  180.         case tc7_smob:
  181.           switch GCTYP16 (scmptr)
  182.         {
  183.         case tc_free_cell:
  184.           if GC8MARKP (scmptr)
  185.             goto c8mrkcontinue;
  186.           break;
  187. #ifdef BIGDIG
  188.         case tcs_bignums:
  189.           if GC8MARKP (scmptr)
  190.             goto c8mrkcontinue;
  191.           m += (NUMDIGS (scmptr) * BITSPERDIG / CHAR_BIT);
  192.           goto freechars;
  193. #endif /* def BIGDIG */
  194.         case tc16_flo:
  195.           if GC8MARKP (scmptr)
  196.             goto c8mrkcontinue;
  197.           switch ((int) (CAR (scmptr) >> 16))
  198.             {
  199.             case (IMAG_PART | REAL_PART) >> 16:
  200.               m += sizeof (double);
  201.             case REAL_PART >> 16:
  202.             case IMAG_PART >> 16:
  203.               m += sizeof (double);
  204.               goto freechars;
  205.             case 0:
  206.               break;
  207.             default:
  208.               goto sweeperr;
  209.             }
  210.           break;
  211.         default:
  212.           if GC8MARKP (scmptr)
  213.             goto c8mrkcontinue;
  214.  
  215.           {
  216.             int k;
  217.             k = SMOBNUM (scmptr);
  218.             if (!(k < scm_numsmob))
  219.               goto sweeperr;
  220.             m += (scm_smobs[k].free) ((SCM) scmptr);
  221.             break;
  222.           }
  223.         }
  224.           break;
  225.         default:
  226.         sweeperr:scm_wta (scmptr, s_bad_type, "gc_sweep");
  227.         }
  228.       n += span;
  229. #if 0
  230.       if (CAR (scmptr) == (SCM) tc_free_cell)
  231.         exit (2);
  232. #endif
  233.       CAR (scmptr) = (SCM) tc_free_cell;
  234.       CDR (scmptr) = nfreelist;
  235.       nfreelist = scmptr;
  236. #if 0
  237.       if ((nfreelist < scm_heap_table[0].bounds[0]) ||
  238.           (nfreelist >= scm_heap_table[0].bounds[1]))
  239.         exit (1);
  240. #endif
  241.       continue;
  242.     c8mrkcontinue:
  243.       CLRGC8MARK (scmptr);
  244.       continue;
  245.     cmrkcontinue:
  246.       CLRGCMARK (scmptr);
  247.     }
  248. #ifdef GC_FREE_SEGMENTS
  249.       if (n == seg_size)
  250.     {
  251.       scm_heap_size -= seg_size;
  252.       scm_must_free ((char *) scm_heap_table[i - 1].bounds[0]);
  253.       scm_heap_table[i - 1].bounds[0] = 0;
  254.       for (j = i; j < scm_n_heap_segs; j++)
  255.         scm_heap_table[j - 1] = scm_heap_table[j];
  256.       scm_n_heap_segs -= 1;
  257.       i -= 1;        /* need to scan segment just moved. */
  258.     }
  259.       else
  260. #endif /* ifdef GC_FREE_SEGMENTS */
  261.     *hp_freelist = nfreelist;
  262.  
  263.       scm_gc_cells_collected += n;
  264.       n = 0;
  265.     }
  266.   scm_lcells_allocated += (   scm_heap_size
  267.                - scm_gc_cells_collected
  268.                - scm_cells_allocated);
  269.   scm_cells_allocated = (scm_heap_size - scm_gc_cells_collected);
  270.   scm_lmallocated -= m;
  271.   scm_mallocated -= m;
  272.   scm_gc_malloc_collected = m;
  273. }
  274.  
  275. STACKITEM * scm_stack_base = 0;
  276.  
  277. #ifdef __STDC__
  278. void
  279. scm_igc (char *what)
  280. #else
  281. void
  282. scm_igc (what)
  283.      char *what;
  284. #endif
  285. {
  286.   int j;
  287.   long oheap_size;
  288.  
  289.   j = scm_num_protects;
  290.   oheap_size = scm_heap_size;
  291.  
  292.   scm_gc_start (what);
  293.   ++scm_errjmp_bad;
  294.  
  295.   {
  296.     SCM type_list;
  297.     SCM * pos;
  298.  
  299.     pos = &type_obj_list;
  300.     type_list = type_obj_list;
  301.     while (type_list != EOL)
  302.       if (VELTS (CAR (type_list))[struct_i_refcnt])
  303.     {
  304.       pos = &CDR (type_list);
  305.       type_list = CDR (type_list);
  306.     }
  307.       else
  308.     {
  309.       *pos = CDR (type_list);
  310.       type_list = CDR (type_list);
  311.     }
  312.   }
  313.  
  314.   while (j--)
  315.     scm_gc_mark (scm_sys_protects[j]);
  316.  
  317.   scm_mark_arrays ();
  318.  
  319.   FLUSH_REGISTER_WINDOWS;
  320.   /* This assumes that all registers are saved into the jmp_buf */
  321.   setjmp (scm_save_regs_gc_mark);
  322.   scm_mark_locations ((STACKITEM *) scm_save_regs_gc_mark,
  323.               (   (sizet) sizeof scm_save_regs_gc_mark
  324.                / sizeof (STACKITEM)));
  325.  
  326.   {
  327.     /* stack_len is long rather than sizet in order to guarantee that
  328.        &stack_len is long aligned */
  329. #ifdef STACK_GROWS_UP
  330. #ifdef nosve
  331.     long stack_len = (STACKITEM *) (&stack_len) - scm_stack_base;
  332. #else
  333.     long stack_len = stack_size (scm_stack_base);
  334. #endif
  335.     scm_mark_locations (scm_stack_base, (sizet) stack_len);
  336. #else
  337. #ifdef nosve
  338.     long stack_len = scm_stack_base - (STACKITEM *) (&stack_len);
  339. #else
  340.     long stack_len = scm_stack_size (scm_stack_base);
  341. #endif
  342.     scm_mark_locations ((scm_stack_base - stack_len), (sizet) stack_len);
  343. #endif
  344.   }
  345.   scm_gc_sweep ();
  346.  
  347.   --scm_errjmp_bad;
  348.   scm_gc_end ();
  349.  
  350.  
  351.   if (oheap_size != scm_heap_size)
  352.     {
  353.       ALLOW_INTS;
  354.       scm_growth_mon ("heap", scm_heap_size, "cells");
  355.       DEFER_INTS;
  356.     }
  357. }
  358.  
  359. extern scm_cell scm_tmp_errp;
  360.  
  361. static char s_not_free[] = "not freed";
  362. #ifdef __STDC__
  363. void 
  364. scm_free_storage (void)
  365. #else
  366. void 
  367. scm_free_storage ()
  368. #endif
  369. {
  370.   sizet i = 0;
  371.  
  372.   DEFER_INTS;
  373.   scm_gc_start ("free");
  374.   ++scm_errjmp_bad;
  375.   cur_inp = BOOL_F;
  376.   cur_outp = BOOL_F;
  377.   cur_errp = PTR2SCM (&scm_tmp_errp);
  378.   scm_gc_mark (def_inp);    /* don't want to close stdin */
  379.   scm_gc_mark (def_outp);    /* don't want to close stdout */
  380.   scm_gc_mark (def_errp);    /* don't want to close stderr */
  381.   scm_gc_sweep ();
  382.   rootcont = BOOL_F;
  383.   while (i < scm_n_heap_segs)
  384.     {                /* free heap segments */
  385.       CELLPTR ptr;
  386.       sizet seg_size;
  387.  
  388.       ptr = CELL_UP (scm_heap_table[i].bounds[0]);
  389.       seg_size = CELL_DN (scm_heap_table[i].bounds[1]) - ptr;
  390.       scm_heap_size -= seg_size;
  391.       scm_must_free ((char *) scm_heap_table[i].bounds[0]);
  392.       scm_heap_table[i].bounds[0] = 0;
  393.       scm_growth_mon ("heap", scm_heap_size, "cells");
  394.       ++i;
  395.     }
  396.   if (scm_heap_size)
  397.     scm_wta (MAKINUM (scm_heap_size), s_not_free, "heap");
  398.  
  399.   /* Not all cells get freed (see scm_gc_mark() calls above). */
  400.   /* if (scm_cells_allocated) scm_wta(MAKINUM(scm_cells_allocated), s_not_free, "cells"); */
  401.   /* either there is a small memory leak or I am counting wrong. */
  402.   /* if (scm_mallocated) scm_wta(MAKINUM(scm_mallocated), s_not_free, "malloc"); */
  403.  
  404.   scm_must_free ((char *) scm_heap_table);
  405.   scm_heap_table = 0;
  406.   scm_must_free ((char *) scm_smobs);
  407.   scm_smobs = 0;
  408.   scm_gc_end ();
  409.   ALLOW_INTS;            /* A really bad idea, but printing does it anyway. */
  410.   scm_exit_report ();
  411.   scm_must_free ((char *) scm_ptobs);
  412.   scm_ptobs = 0;
  413.   scm_lmallocated = scm_mallocated = 0;
  414.   /* Can't do scm_gc_end() here because it uses scm_ptobs which have been freed */
  415. }
  416.  
  417. #ifdef __STDC__
  418. void 
  419. scm_gc_mark (SCM p)
  420. #else
  421. void 
  422. scm_gc_mark (p)
  423.      SCM p;
  424. #endif
  425. {
  426.   register long i;
  427.   register SCM ptr;
  428.  
  429.   ptr = p;
  430.  
  431. gc_mark_loop:
  432.   if (IMP (ptr))
  433.     return;
  434.  
  435. gc_mark_nimp:
  436.   if (NCELLP (ptr))
  437.     scm_wta (ptr, "rogue pointer in ", "heap");
  438.  
  439.   switch (TYP7 (ptr))
  440.     {
  441.     case tcs_cons_nimcar:
  442.       if (GCMARKP (ptr))
  443.     break;
  444.       SETGCMARK (ptr);
  445.       if (IMP (CDR (ptr))) /* IMP works even with a GC mark */
  446.     {
  447.       ptr = CAR (ptr);
  448.       goto gc_mark_nimp;
  449.     }
  450.       scm_gc_mark (CAR (ptr));
  451.       ptr = GCCDR (ptr);
  452.       goto gc_mark_nimp;
  453.     case tcs_cons_imcar:
  454.       if (GCMARKP (ptr))
  455.     break;
  456.       SETGCMARK (ptr);
  457.       ptr = GCCDR (ptr);
  458.       goto gc_mark_loop;
  459.     case tcs_cons_gloc:
  460.       if (GCMARKP (ptr))
  461.     break;
  462.       SETGCMARK (ptr);
  463.       {
  464.     SCM vcell;
  465.     vcell = CAR (ptr) - 1L;
  466.     switch (CDR (vcell))
  467.       {
  468.       default:
  469.         scm_gc_mark (vcell);
  470.         ptr = GCCDR (ptr);
  471.         goto gc_mark_loop;
  472.       case 1:        /* ! */
  473.       case 0:        /* ! */
  474.         {
  475.           char * format;
  476.           int len;
  477.           int i;
  478.           SCM * mem;
  479.           format = CHARS ( ((SCM *)vcell)[struct_i_format] );
  480.           len = LENGTH  ( ((SCM *)vcell)[struct_i_format] );
  481.           mem = (SCM *)GCCDR (ptr);
  482.           for (i = 0; i < len; ++i, ++format)
  483.         if ((*format == 's') || (*format == 'S'))
  484.           scm_gc_mark (mem[i]);
  485.         else if (*format == '*')
  486.           {
  487.             int vlen;
  488.             vlen = mem[i];
  489.             ++format;
  490.             ++i;
  491.             if ((*format == 's') ||  (*format == 'S'))
  492.               {
  493.             int j;
  494.             for (j = 0; j < vlen; ++j)
  495.               scm_gc_mark (mem[i + j]);
  496.               }
  497.           }
  498.         }
  499.         if (!CDR (vcell))
  500.           {
  501.         SETGCMARK (vcell);
  502.         ptr = ((SCM *)vcell)[struct_i_self];
  503.         goto gc_mark_loop;
  504.           }
  505.       }
  506.       }
  507.       break;
  508.     case tcs_closures:
  509.       if (GCMARKP (ptr))
  510.     break;
  511.       SETGCMARK (ptr);
  512.       if (IMP (CDR (ptr)))
  513.     {
  514.       ptr = CLOSCAR (ptr);
  515.       goto gc_mark_nimp;
  516.     }
  517.       scm_gc_mark (CLOSCAR (ptr));
  518.       ptr = GCCDR (ptr);
  519.       goto gc_mark_nimp;
  520.     case tc7_vector:
  521.     case tc7_lvector:
  522. #ifdef CCLO
  523.     case tc7_cclo:
  524. #endif
  525.       if (GC8MARKP (ptr))
  526.     break;
  527.       SETGC8MARK (ptr);
  528.       i = LENGTH (ptr);
  529.       if (i == 0)
  530.     break;
  531.       while (--i > 0)
  532.     if (NIMP (VELTS (ptr)[i]))
  533.       scm_gc_mark (VELTS (ptr)[i]);
  534.       ptr = VELTS (ptr)[0];
  535.       goto gc_mark_loop;
  536.     case tc7_contin:
  537.       if GC8MARKP
  538.     (ptr) break;
  539.       SETGC8MARK (ptr);
  540.       scm_mark_locations (VELTS (ptr),
  541.            (sizet) (LENGTH (ptr) + sizeof (regs) / sizeof (STACKITEM)));
  542.       break;
  543.     case tc7_bvect:
  544.     case tc7_ivect:
  545.     case tc7_uvect:
  546.     case tc7_fvect:
  547.     case tc7_dvect:
  548.     case tc7_cvect:
  549.     case tc7_string:
  550.       SETGC8MARK (ptr);
  551.       break;
  552.     case tc7_msymbol:
  553.       if (GC8MARKP(ptr))
  554.     break;
  555.       SETGC8MARK (ptr);
  556.       scm_gc_mark (SYMBOL_FUNC (ptr));
  557.       ptr = SYMBOL_PROPS (ptr);
  558.       goto gc_mark_loop;
  559.     case tc7_ssymbol:
  560.       if (GC8MARKP(ptr))
  561.     break;
  562.       SETGC8MARK (ptr);
  563.       break;
  564.     case tcs_subrs:
  565.       break;
  566.     case tc7_port:
  567.       i = PTOBNUM (ptr);
  568.       if (!(i < scm_numptob))
  569.     goto def;
  570.       ptr = (scm_ptobs[i].mark) (ptr);
  571.       goto gc_mark_loop;
  572.       break;
  573.     case tc7_smob:
  574.       if (GC8MARKP (ptr))
  575.     break;
  576.       switch TYP16 (ptr)
  577.     { /* should be faster than going through scm_smobs */
  578.     case tc_free_cell:
  579.       /* printf("found free_cell %X ", ptr); fflush(stdout); */
  580.       SETGC8MARK (ptr);
  581.       CDR (ptr) = EOL;
  582.       break;
  583.     case tcs_bignums:
  584.     case tc16_flo:
  585.       SETGC8MARK (ptr);
  586.       break;
  587.     default:
  588.       i = SMOBNUM (ptr);
  589.       if (!(i < scm_numsmob))
  590.         goto def;
  591.       ptr = (scm_smobs[i].mark) (ptr);
  592.       goto gc_mark_loop;
  593.     }
  594.       break;
  595.     default:
  596.     def:scm_wta (ptr, s_bad_type, "gc_mark");
  597.     }
  598. }
  599.  
  600. #ifdef __STDC__
  601. void 
  602. scm_mark_locations (SCM_STACKITEM x[], sizet n)
  603. #else
  604. void 
  605. scm_mark_locations (x, n)
  606.      SCM_STACKITEM x[];
  607.      sizet n;
  608. #endif
  609. {
  610.   register long m = n;
  611.   register int i, j;
  612.   register CELLPTR ptr;
  613.  
  614.   while (0 <= --m)
  615.     if CELLP (*(SCM **) & x[m])
  616.       {
  617.     ptr = (CELLPTR) SCM2PTR ((*(SCM **) & x[m]));
  618.     i = 0;
  619.     j = scm_n_heap_segs - 1;
  620.     if (   PTR_LE (scm_heap_table[i].bounds[0], ptr)
  621.         && PTR_GT (scm_heap_table[j].bounds[1], ptr))
  622.       {
  623.         while (i <= j)
  624.           {
  625.         int seg_id;
  626.         seg_id = -1;
  627.         if (   (i == j)
  628.             || PTR_GT (scm_heap_table[i].bounds[1], ptr))
  629.           seg_id = i;
  630.         else if (PTR_LE (scm_heap_table[j].bounds[0], ptr))
  631.           seg_id = j;
  632.         else
  633.           {
  634.             int k;
  635.             k = (i + j) / 2;
  636.             if (k == i)
  637.               break;
  638.             if (PTR_GT (scm_heap_table[k].bounds[1], ptr))
  639.               {
  640.             j = k;
  641.             ++i;
  642.             if (PTR_LE (scm_heap_table[i].bounds[0], ptr))
  643.               continue;
  644.             else
  645.               break;
  646.               }
  647.             else if (PTR_LE (scm_heap_table[k].bounds[0], ptr))
  648.               {
  649.             i = k;
  650.             --j;
  651.             if (PTR_GT (scm_heap_table[j].bounds[1], ptr))
  652.               continue;
  653.             else
  654.               break;
  655.               }
  656.           }
  657.         if (   !scm_heap_table[seg_id].valid
  658.             || scm_heap_table[seg_id].valid (ptr,
  659.                              &scm_heap_table[seg_id]))
  660.           scm_gc_mark (*(SCM *) & x[m]);
  661.         break;
  662.           }
  663.  
  664.       }
  665.       }
  666. }
  667.  
  668.  
  669.